package com.onpositive.realmview.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class PersistanceUtil {
	public static void readMap(DataInputStream str,
			Map<String, Object> propertyValues) throws IOException {
		if (propertyValues == null)
			propertyValues = new HashMap<String, Object>();
		int count = str.readInt();
		for (int a = 0; a < count; a++) {
			String key = deserializeUTF(str);
			int read = str.read();
			Object value = null;
			switch (read) {
			case 1:
				value = deserializeUTF(str);
				break;
			case 2:
				break;
			case 3:
				value = new Date(str.readLong());
				break;
			case 4:
				value = Long.valueOf(str.readLong());
				break;
			case 5:
				value = Double.valueOf(str.readDouble());
				break;
			case 6:
				value = str.readBoolean();
				break;
			case 7: {
				int len = str.readInt();
				byte[] rs = new byte[len];
				str.readFully(rs);
				ByteArrayInputStream s = new ByteArrayInputStream(rs);
				ObjectInputStream q = new ObjectInputStream(s);
				try {
					value = q.readObject();
					if (value instanceof StringBuilder) {
						value = value.toString();
					}
				} catch (ClassNotFoundException e) {
					throw new IOException(e);
				}
				break;
			}
			case 8:
				int len = str.readInt();
				byte[] rs = new byte[len];
				str.readFully(rs);
				value = rs;
				break;
			}
			propertyValues.put(key, value);
		}
	}

	/**
	 * @param str
	 * @return
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public static void writeMap(DataOutputStream str,
			Map<String, ? extends Object> fields2) throws IOException {
		str.writeInt(fields2.size());
		for (String s : fields2.keySet()) {
			serializeUTF(str, s);
			Object object = fields2.get(s);
			if (object instanceof Collection<?>) {
				// important case
				// TODO FIX ME
				str.write(1);
				StringBuilder bld = new StringBuilder();
				Collection<Object> c = (Collection<Object>) object;
				for (Object o : c) {
					if (o == null) {
						continue;
					}
					bld.append(o.toString());
					bld.append(',');
				}
				if (bld.length() > 0) {
					bld.deleteCharAt(bld.length() - 1);
				}
				// str.writeUTF(bld.toString());
				serializeUTF(str, bld.toString());
				continue;
			}
			if (object instanceof String) {
				str.write(1);
				// str.writeUTF(object.toString());
				serializeUTF(str, object.toString());
				continue;
			} else if (object == null) {
				str.write(2);
				continue;
			} else if (object instanceof Date) {
				str.write(3);
				Date t = (Date) object;
				str.writeLong(t.getTime());
				continue;
			} else if (object instanceof Long) {
				str.write(4);
				Long t = (Long) object;
				str.writeLong(t);
				continue;
			} else if (object instanceof Double) {
				str.write(5);
				Double t = (Double) object;
				str.writeDouble(t);
				continue;
			} else if (object instanceof Boolean) {
				str.write(6);
				Boolean b = (Boolean) object;
				str.writeBoolean(b);
				continue;
			} else if (object instanceof byte[]) {
				str.write(8);
				byte[] n = (byte[]) object;
				str.writeInt(n.length);
				str.write(n);
				continue;
			} else if (object instanceof Serializable) {
				str.write(7);
				Serializable z = (Serializable) object;
				ByteArrayOutputStream out = new ByteArrayOutputStream();
				ObjectOutputStream m = new ObjectOutputStream(out);
				m.writeObject(z);
				m.close();
				byte[] byteArray = out.toByteArray();
				str.writeInt(byteArray.length);
				str.write(byteArray);
				continue;
			}
		}
	}

	/**
	 * @param str
	 * @param s
	 * @throws IOException
	 */
	private static void serializeUTF(DataOutputStream str, String s)
			throws IOException {
		byte[] bytes = s.getBytes(Charset.forName("UTF-8"));
		str.writeInt(bytes.length);
		str.write(bytes);
	}

	private static String deserializeUTF(DataInputStream str)
			throws IOException {
		int size = str.readInt();
		byte[] buff = new byte[size];
		str.readFully(buff);
		String result = new String(buff, 0, buff.length,
				Charset.forName("UTF-8"));
		return result;
	}

	/**
	 * Write serializable object to bytes & write these bytes to {@link DataOutputStream}
	 * @param object object to persist. Should not be null & should be an instance of serializable
	 * @param outputStream {@link DataOutputStream} to write to
	 */
	public static void writeObjectBytes(Object object, DataOutputStream outputStream) {
		if (object == null)
			throw new IllegalArgumentException("Argument cannot be null");
		if (!(object instanceof Serializable))
			throw new RuntimeException(new NotSerializableException(object.getClass().getName() + " should be serializable"));
		ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
		try {
			ObjectOutputStream stream = new ObjectOutputStream(arrayOutputStream);
			stream.writeObject(object);
			byte[] byteArray = arrayOutputStream.toByteArray();
			outputStream.write(byteArray.length);
			outputStream.write(byteArray);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Read serializable object from {@link DataInputStream}
	 * @param inputStream Stram to read from
	 * @return readed object
	 */
	public static Object readObjectFromBytes(DataInputStream inputStream) {
		try {
			int length = inputStream.read();
			byte[] bytes = new byte[length];
			inputStream.read(bytes);
			ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(bytes));
			return stream.readObject();
		} catch (IOException e) {
			throw new RuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
		
	}
}
